﻿using Autodesk.Revit.DB;
using Autodesk.Revit.DB.Events;
using System;
using System.Collections.Generic;
using System.Linq;

using RevitApplication = Autodesk.Revit.ApplicationServices.Application;
using RevitDocument = Autodesk.Revit.DB.Document;

namespace CoreDB.Util
{
    /// <summary>
    /// 用于代替 Revit 的 Transaction
    /// </summary>
    public class TransactionWrapper : IDisposable
    {
        private Transaction m_transaction = null;
        private bool m_committed = false;
        /// <summary>        
        /// 事务将自动开始
        /// </summary>
        /// <param name="document">Revit active document.</param>
        /// <param name="name">The name of the transaction.</param>
        /// <param name="failurePreprocessor">Failure prreprocessor to handle the failures(if have) during transaction commit.</param>
        public TransactionWrapper(RevitDocument document, string name, IFailuresPreprocessor failurePreprocessor = null)
        {
            if ((document != null) && !document.IsModifiable)
                m_transaction = new Transaction(document, name);
            if (m_transaction != null)
            {
                if (null != failurePreprocessor)
                {
                    FailureHandlingOptions failOpt = m_transaction.GetFailureHandlingOptions();
                    failOpt.SetFailuresPreprocessor(failurePreprocessor);
                    m_transaction.SetFailureHandlingOptions(failOpt);
                }
                m_transaction.Start();
            }
        }
        /// <summary>
        /// 通过一个给定的现有事务实例创建一个事务
        /// </summary>
        /// <param name="transaction"></param>
        public TransactionWrapper(Transaction transaction)
        {
            m_transaction = transaction;
            if (m_transaction != null && !transaction.HasStarted())
                m_transaction.Start();
        }
        public TransactionStatus Commit()
        {
            m_committed = true;
            if (m_transaction == null)
                return TransactionStatus.Uninitialized;
            if (m_transaction.HasStarted() && !m_transaction.HasEnded())
                return m_transaction.Commit();
            else
            {
                m_transaction.Dispose();
                return TransactionStatus.Proceed;
            }
        }
        public void Dispose()
        {
            if (m_transaction == null)
                return;
            if (!m_committed)
                m_transaction.RollBack();
            m_transaction.Dispose();
        }
    }
    /// <summary>
    /// 处理警告
    /// </summary>
    public class DeleteWarningsProcessor : IFailuresPreprocessor
    {
        private List<FailureDefinitionId> m_ignoreWarnings = new List<FailureDefinitionId>();
        /// <summary>
        /// 默认处理所有警告
        /// </summary>
        private bool m_ignoreAll = true;
        /// <summary>
        /// 只禁止给定 FDId 的警告
        /// </summary>
        /// <param name="ignoredWarnings"></param>
        public DeleteWarningsProcessor(List<FailureDefinitionId> ignoredWarnings)
        {
            m_ignoreAll = false;
            m_ignoreWarnings = ignoredWarnings;
        }
        /// <summary>
        /// 禁止所有警告。
        /// </summary>
        public DeleteWarningsProcessor() { }
        /// <summary>
        /// Processor to suppress given failure.
        /// </summary>
        /// <param name="failureId"></param>
        public DeleteWarningsProcessor(FailureDefinitionId failureId)
        {
            m_ignoreAll = false;
            m_ignoreWarnings.Add(failureId);
        }
        /// <summary>
        /// 主要接口
        /// </summary>
        /// <param name="failuresAccessor"></param>
        /// <returns></returns>
        public FailureProcessingResult PreprocessFailures(FailuresAccessor failuresAccessor)
        {
            if (m_ignoreAll)
                failuresAccessor.DeleteAllWarnings();
            else
            {
                IList<FailureMessageAccessor> failList = failuresAccessor.GetFailureMessages();
                foreach (FailureMessageAccessor failure in failList)
                {
                    FailureDefinitionId failID = failure.GetFailureDefinitionId();
                    if (m_ignoreWarnings.Contains(failID))
                        failuresAccessor.DeleteWarning(failure);
                }
            }
            return FailureProcessingResult.Continue;
        }
    }
    /// <summary>
    /// 请注意，在解决 Error 期间，所有 Warning 也会被删除。    
    /// </summary>
    public class DeleteErrorsProcessor : IFailuresPreprocessor
    {
        /// <summary>
        /// 
        /// </summary>
        private List<FailureDefinitionId> m_errorIds = new List<FailureDefinitionId>();
        /// <summary>
        /// Default ctor to create processor.
        /// </summary>
        public DeleteErrorsProcessor() { }
        /// <summary>
        /// 创建错误处理器以抑制事务提交期间的给定错误。
        /// </summary>
        /// <param name="errorIds"></param>
        public DeleteErrorsProcessor(List<FailureDefinitionId> errorIds)
        {
            m_errorIds = errorIds;
        }
        /// <summary>
        /// /覆盖实现
        /// </summary>
        /// <param name="failuresAccessor"></param>
        /// <returns></returns>
        public FailureProcessingResult PreprocessFailures(FailuresAccessor failuresAccessor)
        {
            return Procceed(failuresAccessor, m_errorIds);
        }
        /// <summary>
        /// 执行以解决错误。
        /// </summary>
        /// <param name="failuresAccessor"></param>
        /// <param name="errorIds"></param>
        /// <returns></returns>
        public static FailureProcessingResult Procceed(FailuresAccessor failuresAccessor, List<FailureDefinitionId> errorIds)
        {
            // 默认情况下，所有警告都将被禁止
            failuresAccessor.DeleteAllWarnings();

            // 找到所有错误并尝试禁止他们
            bool errorReolved = false;
            IList<FailureMessageAccessor> errors = failuresAccessor.GetFailureMessages(FailureSeverity.Error);
            try
            {
                List<ElementId> elemsToDelete = new List<ElementId>();
                foreach (FailureMessageAccessor fma in errors)
                {
                    if (!fma.HasResolutions())
                        continue;

                    failuresAccessor.ResolveFailure(fma);

                    bool needProceed = true; // always resolve all errors by defauly
                    FailureDefinitionId fdi = fma.GetFailureDefinitionId();
                    //Trace.WriteLine(fdi.Guid.ToString());
                    if (errorIds.Count > 0) // 如果指定了某些所需的错误，则限制为给定的错误。
                    {
                        FailureDefinitionId failID = fma.GetFailureDefinitionId();
                        needProceed = errorIds.Contains(failID);
                    }
                    if (needProceed)
                    {
                        // we force to resovle all failures
                        bool thisResolved = false;
                        foreach (FailureResolutionType frt in Enum.GetValues(typeof(FailureResolutionType)))
                        {
                            if (fma.HasResolutionOfType(frt))
                            {
                                errorReolved = thisResolved = true;
                                fma.SetCurrentResolutionType(frt);
                            }
                        }
                        if (thisResolved)
                        {
                            foreach (var id in fma.GetFailingElementIds())
                                if (!elemsToDelete.Contains(id))
                                    elemsToDelete.Add(id);
                        }
                    }
                }
            }
            catch { }
            if (errorReolved)
            {
                //     The ProceedWithCommit should be used if some or all failures were resolved by the handler.
                //     It instructs the Revit failure processing/transaction mechanism to try to repeat
                //     the transaction committing process. Despite the request to commit to the transaction,
                //     there is no guarantee that the commit will succeed. If errors remain unresolved
                //     the handler will likely be called again. Handler code should be careful not to
                //     try to repeatedly commit if it is unable to deal with all the errors. ProceedWithCommit
                //     cannot be used if the transaction is already being rolled back, and will be treated
                //     as ProceedWithRollBack in this case.   
                return FailureProcessingResult.ProceedWithCommit;
            }
            else
            {
                //     This Continue should be used if no action was taken and/or caller should continue
                //     with default failure processing for the open transaction. In the absence of any
                //     other available handlers, this means that the Revit user interface will display
                //     any errors to the user for resolution. (Warnings will not be displayed if they
                //     have been deleted already by the failure handler). If a FailuresProcessor returns
                //     Continue with unresolved failures, Revit will instead act as if ProceedWithRollBack
                //     was returned.
                return FailureProcessingResult.Continue;
            }
        }
    }
    /// <summary>
    /// 模拟点击原理自动屏蔽 "不可忽略" 的弹窗
    /// </summary>
    public class AutoRollBack : IDisposable
    {
        private RevitApplication m_revitApp = null;
        public AutoRollBack(RevitApplication revitApp)
        {
            m_revitApp = revitApp;
            revitApp.FailuresProcessing += FailuresProcessing;
        }
        public void Dispose()
        {
            if (m_revitApp != null)
                m_revitApp.FailuresProcessing -= FailuresProcessing;
        }
        private void FailuresProcessing(object sender, FailuresProcessingEventArgs e)
        {
            List<FailureMessageAccessor> fmas = e.GetFailuresAccessor().GetFailureMessages().ToList();
            if (fmas.Count == 0)
                e.SetProcessingResult(FailureProcessingResult.Continue);
            else
                e.SetProcessingResult(FailureProcessingResult.ProceedWithRollBack);
        }
    }
    #region 后面的没试过
    /// <summary>
    /// The auto failure resolve event handler for command only.
    /// This class deals with errors onlys, but the warnings will always be deleted.
    /// Note that you don't need to register or unregister FailuresProcessing event as this class will do this automatically. 
    /// </summary>
    public class AutoErrorsProcessor4Command : IDisposable
    {
        private RevitApplication m_revitApp;
        /// <summary>
        /// Create processor which will auto resolve failure.
        /// </summary>
        /// <param name="revitApp"></param>
        public AutoErrorsProcessor4Command(RevitApplication revitApp)
        {
            m_revitApp = revitApp;
            revitApp.FailuresProcessing += FailuresProcessing;
        }
        /// <summary>
        /// Disable the failure processing events
        /// </summary>
        public void Dispose()
        {
            // disable the failure processing
            if (null != m_revitApp)
                m_revitApp.FailuresProcessing -= FailuresProcessing;
        }
        /// <summary>
        /// Event handler implementations to resolve all errors.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public void FailuresProcessing(object sender, Autodesk.Revit.DB.Events.FailuresProcessingEventArgs e)
        {
            FailureProcessingResult result = DeleteErrorsProcessor.Procceed(e.GetFailuresAccessor(), new List<FailureDefinitionId>());
            e.SetProcessingResult(result);
        }
    }

    /// <summary>
    /// CannotJoinElementsError pre-processor
    /// </summary>
    /// <owner>Jim Jia</owner>
    public class UnjoinElementsErrorsProcessor : IFailuresPreprocessor
    {
        /// <summary>
        /// Default ctor to create processor.
        /// </summary>
        public UnjoinElementsErrorsProcessor() { }

        /// <summary>
        /// Override implementations
        /// </summary>
        /// <param name="failuresAccessor"></param>
        /// <returns></returns>
        public FailureProcessingResult PreprocessFailures(FailuresAccessor failuresAccessor)
        {
            // all warnings will be suppressed by default.
            failuresAccessor.DeleteAllWarnings();
            // 
            // find all errors to be suppressed and try resolve them
            bool errorReolved = false;
            IList<FailureMessageAccessor> errors = failuresAccessor.GetFailureMessages(FailureSeverity.Error);
            try
            {
                foreach (FailureMessageAccessor fma in errors)
                {
                    if (!fma.HasResolutions())
                        continue;
                    //
                    // We care about the cannot join only here
                    FailureDefinitionId failId = fma.GetFailureDefinitionId();
                    if (failId == BuiltInFailures.JoinElementsFailures.CannotJoinElementsError)
                    {
                        failuresAccessor.ResolveFailure(fma);
                        errorReolved = true;
                    }
                }
            }
            catch { }
            //
            // return result according to resolution
            if (errorReolved)
            {
                //     The ProceedWithCommit should be used if some or all failures were resolved by the handler.
                //     It instructs the Revit failure processing/transaction mechanism to try to repeat
                //     the transaction committing process. Despite the request to commit to the transaction,
                //     there is no guarantee that the commit will succeed. If errors remain unresolved
                //     the handler will likely be called again. Handler code should be careful not to
                //     try to repeatedly commit if it is unable to deal with all the errors. ProceedWithCommit
                //     cannot be used if the transaction is already being rolled back, and will be treated
                //     as ProceedWithRollBack in this case.   
                return FailureProcessingResult.ProceedWithCommit;
            }
            else
            {
                //     This Continue should be used if no action was taken and/or caller should continue
                //     with default failure processing for the open transaction. In the absence of any
                //     other available handlers, this means that the Revit user interface will display
                //     any errors to the user for resolution. (Warnings will not be displayed if they
                //     have been deleted already by the failure handler). If a FailuresProcessor returns
                //     Continue with unresolved failures, Revit will instead act as if ProceedWithRollBack
                //     was returned.
                return FailureProcessingResult.Continue;
            }
        }
    }

    /// <summary>
    /// CannotJoinElementsError pre-processor
    /// </summary>
    /// <owner>Jim Jia</owner>
    public class AutoErrorsResolver : IFailuresPreprocessor
    {
        /// <summary>
        /// The erros to resolve.
        /// </summary>
        public List<FailureDefinitionId> Errors { get; set; }

        /// <summary>
        /// Default ctor to create processor.
        /// </summary>
        public AutoErrorsResolver(List<FailureDefinitionId> errors)
        {
            Errors = new List<FailureDefinitionId>();
            Errors.AddRange(errors);
        }

        /// <summary>
        /// Override implementations
        /// </summary>
        /// <param name="failuresAccessor"></param>
        /// <returns></returns>
        public FailureProcessingResult PreprocessFailures(FailuresAccessor failuresAccessor)
        {
            // all warnings will be suppressed by default.
            failuresAccessor.DeleteAllWarnings();
            // 
            // find all errors to be suppressed and try resolve them
            bool errorReolved = false;
            IList<FailureMessageAccessor> errors = failuresAccessor.GetFailureMessages(FailureSeverity.Error);
            try
            {
                foreach (FailureMessageAccessor fma in errors)
                {
                    if (!fma.HasResolutions())
                        continue;
                    //
                    // Checks to see if errors is desired one
                    FailureDefinitionId failId = fma.GetFailureDefinitionId();
                    if (Errors.Contains(failId))
                    {
                        failuresAccessor.ResolveFailure(fma);
                        errorReolved = true;
                    }
                }
            }
            catch { }
            //
            // return result according to resolution
            if (errorReolved)
                return FailureProcessingResult.ProceedWithCommit;
            else
                return FailureProcessingResult.Continue;
        }
        #endregion

    }
}
